package com.tianji.learning.service.impl;


import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constans.RedisConstans;
import com.tianji.learning.domian.vo.SignResultVO;
import com.tianji.learning.service.ISignRecordService;
import com.tianji.learning.mq.message.SignInMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;
import static com.tianji.common.utils.DateUtils.SIGN_DATE_SUFFIX_FORMATTER;

@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {
    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;


    /*添加签到记录*/
    @Override
    public SignResultVO addSignRecord() {
        //1.签到
        //1.1.获取签到信息
        Long userId = UserContext.getUser();

        //1.2.获取日期
        LocalDate now = LocalDate.now();
        String dateStr = now.format(SIGN_DATE_SUFFIX_FORMATTER);

        //1.3.拼接key
        String key = RedisConstans.SIGN_RECORD_KEY_PREFIX + userId + dateStr;

        //1.4.计算offset值
        int offset = now.getDayOfMonth() - 1;

        //1.5.保存签到信息（执行）setbit
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)){
            throw new BizIllegalException("不允许重复签到");
        }

        //2.计算连续签到天数
        int signDays = countSignDays(key,now.getDayOfMonth());
        //3.计算签到奖励积分
        int rewardPoints = getRewardPoints(signDays);
        //4.保存积分明细记录
        try {
            mqHelper.send(LEARNING_EXCHANGE,SIGN_IN,SignInMessage.of(userId, rewardPoints + 1));
        } catch (Exception e) {
            log.error("签到发送新增积分信息失败");
        }

        //5.封装返回vo
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);

        return vo;
    }

    private int getRewardPoints(int signDays) {
        switch (signDays){
            case 7:
                return 10;
            case 14:
                return 20;
            case 28:
                return 40;
            default:
                return 0;
        }

    }

    private int countSignDays(String key, int len) {
        //1.执行bitfield命令操作，获取本月从第一天开始到今天为止的所有签到记录
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(len)
                ).valueAt(0));

        int signResult = result.get(0).intValue();

        //2.定义一个计算器
        int day = 0;

        //3.循环，与1做与运算，得到最后一个bit ， 判断是否为1，如果为1，则计算天数加1，否则结束循环
        while ((signResult & 1) == 1){
            //4.计算器 + 1
            day++;
            //5、把数字右移一位，最后一位被舍弃，倒数第二位就成了最后一位
            signResult >>>= 1;
        }

        return day;
    }

    /*查询签到记录*/
    @Override
    public Byte[] querySignRecords() {
        //1.获取key
        //1.1.获取用户id
        Long userId = UserContext.getUser();
        //1.2.获取日期
        LocalDateTime now = LocalDateTime.now();
        String dateStr = now.format(SIGN_DATE_SUFFIX_FORMATTER);

        //1.3.拼接key
        String key = RedisConstans.SIGN_RECORD_KEY_PREFIX + userId + dateStr;

        //2.查询截止到当天的签到记录
        int dayOfMonth = now.getDayOfMonth();
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)
                ).valueAt(0));

        int signResult = result.get(0).intValue();

        //3.循环获取每一天的签到情况，赋值给 byte数组
        Byte[] arr = new Byte[dayOfMonth];

        int pos = dayOfMonth - 1;
        while (pos >= 0){
            arr[pos--] = (byte) (signResult &1);
            signResult >>>= 1;
        }

        return arr;
    }


}
